-
Notifications
You must be signed in to change notification settings - Fork 3
Add SphericalHarmonicsAudio classes #166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
f-brinkmann
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the effort. It generally looks good to me! I suggested one structural change and adding the docs but otherwise its fine to me. I think the testing should be fine. complete functionality is tested for SH signal, initialization tested for each class separately.
spharpy/classes/audio.py
Outdated
| # check dimensions | ||
| if len(data.shape) < 3: | ||
| raise ValueError("Invalid number of dimensions. Data should have " | ||
| "at least 3 dimensions.") | ||
|
|
||
| # set n_max | ||
| n_max = np.sqrt(data.shape[-2])-1 | ||
| if n_max - int(n_max) != 0: | ||
| raise ValueError("Invalid number of SH channels: " | ||
| f"{data.shape[-2]}. It must match (n_max + 1)^2.") | ||
| self._n_max = int(n_max) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems a little redundant to me to have this in all three classes. Would the following work:
- First call the init of the Audio class (FrequencyData in this case)
- Second call the init of the SHAudio class and move this code there
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, yes that works.
spharpy/classes/audio.py
Outdated
|
|
||
|
|
||
| class SphericalHarmonicTimeData(SphericalHarmonicsAudio, TimeData): | ||
| """_summary_ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing docs - probably intended to get a review before the effort.
spharpy/classes/audio.py
Outdated
|
|
||
|
|
||
| class SphericalHarmonicFrequencyData(SphericalHarmonicsAudio, FrequencyData): | ||
| """_summary_ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing docs - probably intended to get a review before the effort.
add docstrings move n_max and dimensions check to base class
f-brinkmann
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me. I would suggest to wait with the merge until we finalized discussions on the SH conventions that we want to support. This might cause changes here as well.
ahms5
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for taking care!
just minor comments
spharpy/classes/audio.py
Outdated
| class SphericalHarmonicSignal(Signal): | ||
| """Create audio object with spherical harmonics coefficients in time or | ||
| frequency domain. | ||
| class SphericalHarmonicAudio(_Audio): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| class SphericalHarmonicAudio(_Audio): | |
| class _SphericalHarmonicAudio(_Audio): |
shouldnt this be private, too?
spharpy/classes/audio.py
Outdated
| comment=comment, is_complex=is_complex) | ||
| SphericalHarmonicAudio.__init__( | ||
| self, basis_type, normalization, channel_convention, | ||
| condon_shortley, domain="time", comment=comment) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| condon_shortley, domain="time", comment=comment) | |
| condon_shortley, domain=domain, comment=comment) |
domain="time"would overwrite the given domain. But i think we can remove it here anyway
spharpy/classes/audio.py
Outdated
| def __init__(self, basis_type, normalization, channel_convention, | ||
| condon_shortley, domain, comment=""): | ||
| _Audio.__init__(self, domain=domain, comment=comment) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| def __init__(self, basis_type, normalization, channel_convention, | |
| condon_shortley, domain, comment=""): | |
| _Audio.__init__(self, domain=domain, comment=comment) | |
| def __init__(self, basis_type, normalization, channel_convention, | |
| condon_shortley): |
i wounder if we really need it here, because it would be set twice, by the audio class and then overwritten by this class. I think the _Audio.init is not setting anything else then this to entries, so i guess we can remove it.
spharpy/classes/audio.py
Outdated
| SphericalHarmonicAudio.__init__( | ||
| self, basis_type, normalization, channel_convention, | ||
| condon_shortley, domain="time", comment=comment) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| SphericalHarmonicAudio.__init__( | |
| self, basis_type, normalization, channel_convention, | |
| condon_shortley, domain="time", comment=comment) | |
| SphericalHarmonicAudio.__init__( | |
| self, basis_type, normalization, channel_convention, | |
| condon_shortley) |
|
Thanks for implementing! I think I am missing something fundamental here. Why is it not possible that _SphericalHarmonicAudio inherits from the SphericalHarmonics class? |
Because the SphericalHarmonics class contains a number of properties which are not meaningful in the SphericalHarmonicSignal class. They however share a set of properties/methods which would make a shared base class usefull, see #173 |
I'm very sorry, I meant to write don't rename in this branch, since the file is already part of develop. |
mberz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm just commenting on this now, as I feel we need to make some adjustments to the SphericalHarmonicDefinition class before continuing with this PR.
- I think it makes sense to move the functionality of saving the old properties to the parent class, as it could come in handy in other classes.
- I now remember why I first had the SphericalHarmonicDefintion in mind without the
n_maxproperty defined. I'll add an abstract base class for that which could serve as parent for the SphericalHarmonicAudio classes.
Sorry to postpone this PR once again.
Always store fully normalized data in ACN order and renormalize accordinglz in setter/getter
f-brinkmann
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following up on the question on slack, I think it would be cleaner to keep data out of _SHAudio. Could this work:
- move checks for
n_maxfrom_SHAudio.__init__to a private method, e.g.,_SHAudio._check_data_shape. This avoids the need to passdatato init. - Call this private method in each setter, e.g.,
SHTimeData.time - In each init, e.g.,
SHTimeDatafirst initialize_SHAudio, than call the private method for checking the data shape, and finally callTimeData.__init__
Other points:
Instead of inheriting from _SphericalHarmonicBase and re-implementing the n_max getter, I think it would be cleaner to inherit from SphericalHarmonicDefinition and
- overload the
n_maxsetter to raise a ValueError, e.g., 'n_max can not be set and is defined by the shape of the data contained in the spherical harmonic Audio object' - overload the
basis_typesetter to raise a ValueError, e.g., 'Setting the basis_type is not yet supported'. This could be implemented at a later point, if we want. Since we decided to internally store data in standard conventions, this would be fairly straigt forward in this case.
mberz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a couple of questions which may help to avoid the missing self._data problem.
spharpy/classes/audio.py
Outdated
| condon_shortley): | ||
|
|
||
| # check dimensions | ||
| if len(self._data.shape) < 3: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if len(self._data.shape) < 3: | |
| if len(self.cshape) < 2: |
spharpy/classes/audio.py
Outdated
| "at least 3 dimensions.") | ||
|
|
||
| # calculate n_max | ||
| n_max = np.sqrt(self._data.shape[-2])-1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| n_max = np.sqrt(self._data.shape[-2])-1 | |
| n_max = np.sqrt(self._data.cshape[-1])-1 |
spharpy/classes/audio.py
Outdated
| raise ValueError("Invalid number of SH channels: " | ||
| f"{self._data.shape[-2]}. It must match " | ||
| "(n_max + 1)^2.") | ||
| self._n_max = int(n_max) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| self._n_max = int(n_max) |
spharpy/classes/audio.py
Outdated
| @property | ||
| def n_max(self): | ||
| """Get or set the spherical harmonic order.""" | ||
| return self._n_max |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| return self._n_max | |
| return int(np.sqrt(self._data.cshape[-1])-1) |
spharpy/classes/audio.py
Outdated
|
|
||
| class SphericalHarmonicSignal(Signal): | ||
| """Create audio object with spherical harmonics coefficients in time or | ||
| class _SphericalHarmonicAudio(_Audio, _SphericalHarmonicBase): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| class _SphericalHarmonicAudio(_Audio, _SphericalHarmonicBase): | |
| class _SphericalHarmonicAudio(_Audio, _SphericalHarmonicBase, ABC): |
| FrequencyData.freq.fset(self, value) | ||
|
|
||
|
|
||
| class SphericalHarmonicSignal(_SphericalHarmonicAudio, Signal): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I do not fully get the concept, but shouldn't this be
| class SphericalHarmonicSignal(_SphericalHarmonicAudio, Signal): | |
| class SphericalHarmonicSignal(SphericalHarmonicTimeData, SphericalHarmonicFrequencyData): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's true. With the new inheritation structure we need that. I have it changed locally already :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not obsolete, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup
manually integrate branch #229
allow to pass sh data with dimensions < 3
f-brinkmann
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Structure and docs are fine to me. I found a few small things that still should be changed.
| @property | ||
| def n_max(self): | ||
| """Get or set the spherical harmonic order.""" | ||
| return int(np.sqrt(self.cshape[-1])-1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think overwriting the setter for basis_type must still be done to raise an AttributeError along the lines of 'Changing the basis_type' is not yet supported'.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll take a closer look at that point. I tried this real quick, but if we overwrite it, we cannot init _SphericalHarmonicBase because it calls the basis_type setter :D Let me check if there is a way without changing _SphericalHarmonicBase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found a solution - not super beautiful but works
spharpy/classes/audio.py
Outdated
| Adds a singleton dimensions at the front if necessary. | ||
| """ | ||
|
|
||
| return data[np.newaxis, ...] if data.ndim < 3 else data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without this change, a 2D array could be returned if passing a 1D array:
| return data[np.newaxis, ...] if data.ndim < 3 else data | |
| return np.atleast_2d(data)[np.newaxis, ...] if data.ndim < 3 else data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice catch
| FrequencyData.freq.fset(self, value) | ||
|
|
||
|
|
||
| class SphericalHarmonicSignal(_SphericalHarmonicAudio, Signal): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not obsolete, right?
| def n_max(self): | ||
| """Get the maximum spherical harmonic order.""" | ||
| return self._n_max | ||
| def freq(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The getter and setter for freq_raw must be overwritten as well, I think.
overwrite basis_type setter and freq_raw
Which issue(s) are closed by this pull request?
Closes #165
Changes proposed in this pull request: